//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
// (c) Philipp Peshin (phil@mobilae.ru), 2003
// (c) Alexander Litvinov (lan@ac-sw.com), 2006
//
// Object selection handling.
//

# include "StdAfx.h"
# include "Selection.h"
# include "Position.h"
# include "Scale.h"
# include "MapView.h"
# include "Menu.h"
# include "Load.h"
# include "Levels.h"
# include "TypesDraw.h"
# include "DrawNumbers.h"
# include "fast_floor.h"
# include "Globals.h"

find_info_m_t g_Selection;

find_info_t FindRgn (map_t & _map, int _x, int _y, const rgn_t * _pExceptRgn) {
	if (g_bLoading)
		return find_info_t ();

	CCritSec::CLock lock (g_cs);

	point_t p;
	p.x = g_map.rectBound.x0 + g_X + _x/g_kX;
	p.y = g_map.rectBound.y0 + g_Y + (g_lHeight - _y)/g_kY;

	return _map.Find (p, _pExceptRgn);
}

find_info_t FindRgn (int _x, int _y, const rgn_t * _pExceptRgn) {
	return FindRgn (g_map, _x, _y, _pExceptRgn);
}

bool StickPointToNodeOrGrid (size_t _cLevelIdx, int & _x, int & _y, point_t & _pointStick, const rgn_t * _pExceptRgn) {
	if (! g_bEnableSticking) {
		ScreenToPt (_x, _y, _pointStick);
		g_map.pTypeSet->pfRoundPoint (g_map, _cLevelIdx, _pointStick);
		_x = XToScreen (_pointStick.x);
		_y = YToScreen (_pointStick.y);

		return false;
	}

	const find_info_t fiStickTo = FindRgn (_x, _y, _pExceptRgn);

    bool bRes = (fiStickTo.pLayer != NULL);
    if (bRes) {
        const rgn_t & rgn = * fiStickTo.iRgn;
        if (rgn.IsPolygon () || rgn.IsPolyline ()) {
		    // The precision of matching in degrees.
		    const float c_fE = c_fE_in_pixels/g_kY;

            const points_t & pts = rgn.elements [fiStickTo.cElementOrOrigin].points;
            const size_t cIndex = PointsContainPt (pts, fiStickTo.ptNearest, c_fE, g_map.fCosY);
            bRes = (cIndex != -1);
            if (bRes)
	            _pointStick = pts [cIndex];
        } else {
            // All POI/points stick exactly.
            bRes = true;
            _pointStick = fiStickTo.ptNearest;
        }
    }
    if (! bRes)
        ScreenToPt (_x, _y, _pointStick);

    g_map.pTypeSet->pfRoundPoint (g_map, _cLevelIdx, _pointStick);
	_x = XToScreen (_pointStick.x);
	_y = YToScreen (_pointStick.y);

	return bRes;
}

///////////////////////////////////////////////////////////////////////

static
bool IsInSelection (find_info_t & _SelItem) {
	// TODO: check elements.
	const find_info_m_t::iterator i = std::find (g_Selection.begin (), g_Selection.end (), _SelItem);
	return i != g_Selection.end ();
}

bool SelectObjectAt (int _x, int _y, bool _bAdd, bool _bElementOnly, bool _bEnableRedraw) {
	find_info_t SelItem = FindRgn (_x, _y);
	if (SelItem.pLayer && ! _bElementOnly)
		SelItem.cElementOrOrigin = -1;

	bool bRedraw = false;
	bool bInsideSelection = false;

	if (SelItem.empty ()) {
		if (! _bAdd) {
			// Reset the selection.
			g_Selection.clear ();

			bRedraw = true;
		} else {
			// Do not modify the selection.
		}
	} else {
		const find_info_m_t::iterator i = std::find (g_Selection.begin (), g_Selection.end (), SelItem);
		if (i != g_Selection.end ()) {
			// NOTE: The item is already selected.
			if (_bAdd) {
				// Exclude it from the selection.
				// TODO: handle whole & elements as well.
				g_Selection.erase (i);
				bRedraw = true;
			} else {
				// Do not modify the selection but prepare to drag it.
				bInsideSelection = true;

				// NOTE: re-selection may be at different point.
				i->cPoint = SelItem.cPoint;
			}
		} else {
			if (! _bAdd)
				// Reset the selection.
				g_Selection.clear ();

			// Include to the selection.
			// TODO: handle whole & elements as well.
			g_Selection.push_back (SelItem);

			bRedraw = true;
			bInsideSelection = true;
		}
	}

	// Redraw map with the new selection.
	if (bRedraw && _bEnableRedraw) {
		Redraw ();
		UpdateMenu ();
	}

	return bInsideSelection;
}

void SelectObjectsInsideRect (const rect_t & _rectSelection, bool _bAdd, bool _bElementOnly) {
    if (! _bAdd)
        g_Selection.clear ();

    find_info_t SelItem;
    for (int cKind = rkMax - 1; cKind >= 0; -- cKind) {
		layer_t & layer = g_map.layers [cKind];
		rgns_t & rgns = layer.rgns;
		for (rgns_t::iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			rgn_t & rgn = * iRgn;

			if (rgn.IsDeleted ())
				continue;
			if (rgn.IsWTR ()) {
				if (! g_bShowAttachments)
					continue;
				if (rgn.pFileRef && ! rgn.pFileRef->bVisible)
					continue;
			}

            if (! rgn.rectBound.Intersects (_rectSelection)) 
                continue;

			bool bAddRgn = false;

			// Match origins.
			for (size_t cOrigin = 0; cOrigin < rgn.origins.size (); ++ cOrigin) {
				const rgn_origin_t & origin = rgn.origins [cOrigin];
				if (origin.bDeleted)
					continue;

				if (! rgn.IsWTR () && ! LevelIsVisible (origin.cLevel, rgn.uchEndLevel))
					continue;

				if (_rectSelection.Contains (origin.point)) {
					if (_bElementOnly) {
						SelItem.pLayer = & layer;
						SelItem.iRgn = iRgn;
						SelItem.cElementOrOrigin = cOrigin;

						g_Selection.push_back (SelItem);

						continue;
					} else
						bAddRgn = true;
				}
			}
			
			// Match elements.
			for (size_t cElement = 0; cElement < rgn.elements.size (); ++ cElement) {
				const rgn_element_t & element = rgn.elements [cElement]; 
				if (element.bDeleted)
					continue;

				const points_t & points = element.points;
				if (points.empty ())
					continue;

				if (! element.rectBound.Intersects (_rectSelection))
					continue;

				if (! rgn.IsWTR () && ! LevelIsVisible (element.cLevel, rgn.uchEndLevel))
					continue;

                if (RectIntersectsPolyline (_rectSelection, points, rgn.IsPolygon ())) {
					if (_bElementOnly) {
						SelItem.pLayer = & layer;
						SelItem.iRgn = iRgn;
						SelItem.cElementOrOrigin = cElement;

						if (! IsInSelection (SelItem))
							g_Selection.push_back (SelItem);
					} else
						bAddRgn = true;
                }
            }

			if (bAddRgn) {
				SelItem.pLayer = & layer;
				SelItem.iRgn = iRgn;
				SelItem.cElementOrOrigin = -1;

				if (! IsInSelection (SelItem))
					g_Selection.push_back (SelItem);
			}
        }
    }
}

void SelectAllObjects (bool _bObjects, bool _bTracks, bool _bWaypoints, bool _bRoutes, bool _bRasters) {
	g_Selection.clear ();

    find_info_t SelItem;
    for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
		layer_t & layer = g_map.layers [cKind];
		rgns_t & rgns = layer.rgns;
		for (rgns_t::iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			rgn_t & rgn = * iRgn;

			if (rgn.IsDeleted ())
				continue;

			bool bAdd = false;
			if (_bObjects   && ! rgn.IsWTR ())
				bAdd = true;
			if (_bTracks    && rgn.IsTrack ())
				bAdd = true;
			if (_bWaypoints && rgn.IsWaypoint ())
				bAdd = true;
			if (_bRoutes    && rgn.IsRoute ())
				bAdd = true;

			if (bAdd) {
				SelItem.pRenderer        = NULL;
				SelItem.pLayer           = & layer;
				SelItem.iRgn             = iRgn;
				SelItem.cElementOrOrigin = -1;

				g_Selection.push_back (SelItem);
			}
		}
	}

	if (_bRasters) {
		for (renderers_t::iterator iRenderer = g_map.renderers.begin (); iRenderer != g_map.renderers.end (); ++ iRenderer) {
			renderer_base_t * const pRenderer = * iRenderer;
			if (pRenderer->bDeleted || ! pRenderer->bVisible)
				continue;
			
			SelItem.pRenderer        = pRenderer;
			SelItem.pLayer           = NULL;
			SelItem.cElementOrOrigin = -1;

			g_Selection.push_back (SelItem);
		}
	}

	// Redraw map with selection.
	Redraw ();
	UpdateMenu ();
}

void UnselectObjects () {
	if (g_Selection.empty ())
		return;

	g_Selection.clear ();

	// Redraw map without selection.
	Redraw ();
	UpdateMenu ();
}

////////////////////////////////////////////////////

bool IsInvertSelectionDisabled () {
	const size_t cSelItems = g_Selection.size ();
	for (size_t cSelItem = 0; cSelItem < cSelItems; ++ cSelItem) {
		const find_info_t & SelItem = g_Selection [cSelItem];

		// Check if we have raster image selection.
		if (SelItem.pLayer == NULL)
			return true;

		// Check if we have element selection.
		if (SelItem.cElementOrOrigin != -1)
			return true;
	}

	return false;
}

void InvertSelection () {
	// Clear temporary flag at all rgns.
	size_t cKind;
    for (cKind = 0; cKind < rkMax; ++ cKind) {
		layer_t & layer = g_map.layers [cKind];
		rgns_t & rgns = layer.rgns;
		for (rgns_t::iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			rgn_t & rgn = * iRgn;

			rgn.bTemporary = false;
		}
	}

	// Set temp flag for selected rgns.
	if (! g_Selection.empty ()) {
		for (find_info_m_t::iterator selIt = g_Selection.begin (); selIt != g_Selection.end (); ++ selIt) {
			find_info_t & info = * selIt;
	        rgn_t & rgn = * info.iRgn;

			rgn.bTemporary = true;
		}
	}

	// Create new selection.
	g_Selection.clear ();

    find_info_t SelItem;
	for (cKind = 0; cKind < rkMax; ++ cKind) {
		layer_t & layer = g_map.layers [cKind];
		rgns_t & rgns = layer.rgns;
		for (rgns_t::iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			rgn_t & rgn = * iRgn;

			if (rgn.IsDeleted ())
				continue;

			if (rgn.bTemporary == false) {
				SelItem.pLayer           = & layer;
				SelItem.iRgn             = iRgn;
				SelItem.cElementOrOrigin = -1;

				g_Selection.push_back (SelItem);
				
			}
			rgn.bTemporary = false;
		}
	}

	// Redraw map without selection.
	Redraw ();
	UpdateMenu ();
}

////////////////////////////////////////////////////

void SelectObjectsInsidePolygon (const rgn_t & _rgnSelection, bool _bAdd) {
	assert (_rgnSelection.Kind == rkPolygon);
	if (_rgnSelection.Kind != rkPolygon)
		return;

    if (! _bAdd)
        g_Selection.clear ();

	const float c_fE = c_fE_in_pixels/g_kY;

    find_info_t SelItem;
    for (int cKind = rkMax - 1; cKind >= 0; -- cKind) {
		layer_t & layer = g_map.layers [cKind];
		rgns_t & rgns = layer.rgns;
		for (rgns_t::iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			rgn_t & rgn = * iRgn;

			if (& rgn == & _rgnSelection)
				continue;

			if (rgn.IsDeleted () || rgn.IsWTR ())
				continue;

			if (! _rgnSelection.rectBound.Intersects (rgn.rectBound))
				continue;

			bool bSelectRgn = false;
			if (rgn.Kind == rkPoint) {
				for (rgn_origins_t::const_iterator orgIt = rgn.origins.begin(); orgIt != rgn.origins.end (); ++ orgIt) {
					const rgn_origin_t & org = * orgIt;
					bSelectRgn = _rgnSelection.Contains (org.point, c_fE);
					if (bSelectRgn)
						break;
				}
			} else {
				assert (rgn.Kind == rkPolyline || rgn.Kind == rkPolygon);

				for (rgn_elements_t::const_iterator elmIt = rgn.elements.begin (); elmIt != rgn.elements.end (); ++ elmIt) {
					const rgn_element_t & elm = * elmIt;
					
					const point_t * pPrevPoint = NULL;
					for (points_t::const_iterator ptIt = elm.points.begin (); ptIt != elm.points.end (); ++ ptIt) {
						const point_t & pt = * ptIt;
						bSelectRgn = _rgnSelection.Contains (pt, c_fE);
						
						if (! bSelectRgn && pPrevPoint != NULL)
							bSelectRgn = rgn.IntersectsSegment (* pPrevPoint, pt);
						if (bSelectRgn)
							break;
						
						pPrevPoint = & pt;
					}

					if (bSelectRgn)
						break;
				}
			}

			if (bSelectRgn) {
				SelItem.pLayer           = & layer;
				SelItem.iRgn             = iRgn;
				SelItem.cElementOrOrigin = -1;

				if (! IsInSelection (SelItem))
					g_Selection.push_back (SelItem);
			}
        }
    }
}

//////////////////////////////////////////

static int g_iXOffset;
static int g_iYOffset;

static
void _DrawSelection (HDC _hDC, const rect_t & _rectVisible, const find_info_t & _SelItem) {
	if (g_bShowAttachments && _SelItem.pRenderer) {
		const renderer_base_t & renderer = * _SelItem.pRenderer;
		if (renderer.bDeleted || ! renderer.bVisible)
			return;
		if (! _rectVisible.Intersects (renderer.rect))
			return;

		const long lX = XToScreen (renderer.rect.x0) + g_iXOffset;
		const long lY = YToScreen (renderer.rect.y1) + g_iYOffset;
		const long lW = fast_floor (renderer.rect.Width  ()*g_kX + .5f);
		const long lH = fast_floor (renderer.rect.Height ()*g_kY + .5f);

		::SetBkMode    (_hDC, TRANSPARENT);
		::SetBkColor   (_hDC, c_colWhite);
		::SetTextColor (_hDC, c_colBlack); // NOTE: it really has undocumented influence!
		::SelectObject (_hDC, g_bTransparentPolygons ? g_hbrSelectionTransp : g_hbrSelection);
		::PatBlt       (_hDC, lX, lY, lW, lH, PATINVERT);
		
		return;
	}

	if (! _SelItem.pLayer)
		return;
	const rgn_t * const pSelectedRgn = & * _SelItem.iRgn;
	if (! pSelectedRgn)
		return;

	CCritSec::CLock lock (g_cs);

	if (pSelectedRgn->IsDeleted ())
		return;
	if (pSelectedRgn->IsWTR ()) {
		if (! g_bShowAttachments)
			return;
		if (pSelectedRgn->pFileRef && ! pSelectedRgn->pFileRef->bVisible)
			return;
	}

	if (! _rectVisible.Intersects (pSelectedRgn->rectBound))
		return;

	const type_desc_t * const pTD = pSelectedRgn->GetTypeDesc ();
	if (! pTD)
		return;

	const bool bHuge = g_map.cMaxPointsInElement > 30000;
	// NOTE: reserve +1 additional point to store copy of first point to be able to complete polygon's perimeter.
	POINT * const pts = bHuge ? new POINT [g_map.cMaxPointsInElement + 1] : reinterpret_cast<POINT *> (alloca (sizeof (POINT)*(g_map.cMaxPointsInElement + 1)));
	CArrayHolder<POINT> pts_Holder (bHuge ? pts : NULL);

	const int iXOffset = pSelectedRgn->IsWTR () ? 0 : g_iXOffset;
	const int iYOffset = pSelectedRgn->IsWTR () ? 0 : g_iYOffset;

	const rgn_elements_t & elements = pSelectedRgn->elements;
	for (size_t cElement = 0; cElement < elements.size (); ++ cElement) {	
		const rgn_element_t & element = elements [cElement];
		if (element.bDeleted)
			continue;

		const points_t & points = element.points;
		const size_t cPoints = points.size ();

		if (_SelItem.cElementOrOrigin != -1 && _SelItem.cElementOrOrigin != cElement)
			continue;

		if (! pSelectedRgn->IsWTR () && ! LevelIsVisible (element.cLevel, pSelectedRgn->uchEndLevel))
			continue;

		if (! _rectVisible.Intersects (element.rectBound))
			continue;

		// Calculate screen coordinates.
		size_t cPoint;
		for (cPoint = 0; cPoint < cPoints; ++ cPoint) {
			const point_t & pt = points [cPoint];
			pts [cPoint].x = XToScreen (pt.x) + iXOffset;
			pts [cPoint].y = YToScreen (pt.y) + iYOffset;
		}
		pts [cPoint].x = pts [0].x;
		pts [cPoint].y = pts [0].y;

		if (pSelectedRgn->IsPolygon ()) {
			// NOTE: XORing gray solid brush has no effect.
			bool bGrayBrush = false;
			if (pTD->phBrush) {
				LOGBRUSH lb;
				::GetObject (* pTD->phBrush, sizeof (lb), & lb);
				bGrayBrush = lb.lbColor == c_colGray && lb.lbStyle == BS_SOLID;
			}

			// Fill polygon with selection pattern.
			::SelectObject (_hDC, g_hpenNull);
			::SelectObject (_hDC, g_bTransparentPolygons ? g_hbrSelectionTransp : g_hbrSelection);
			::SetBkMode (_hDC, TRANSPARENT);
			::SetBkColor (_hDC, c_colWhite);
			::SetTextColor (_hDC, c_colBlack); // NOTE: it really has undocumented influence!
			::SetROP2 (_hDC, bGrayBrush ? R2_MERGEPEN : R2_XORPEN);
			::Polygon (_hDC, pts, cPoints);

			// Outline perimeter.
			::SelectObject (_hDC, g_hpenBlackThin);
			::SetROP2      (_hDC, R2_COPYPEN);
			::Polyline (_hDC, pts, cPoints + 1);
		} else {
			// Draw polyline.
			::SelectObject (_hDC, g_hpenSelection);
			::SetBkMode    (_hDC, OPAQUE);
			::SetBkColor   (_hDC, c_colWhite);
			::SetROP2      (_hDC, R2_COPYPEN);

			::Polyline (_hDC, pts, cPoints);

			DrawNumbersSelected (_hDC, * pSelectedRgn, element, pts, cPoints);
		}
	}

	// Draw POI.
	if (pTD->phCursor) {
		const rgn_origins_t & origins = pSelectedRgn->origins; 
		for (size_t cOrigin = 0; cOrigin < origins.size (); ++ cOrigin) {
			const rgn_origin_t & origin = origins [cOrigin];
			if (origin.bDeleted)
				continue;

			if (_SelItem.cElementOrOrigin != -1 && _SelItem.cElementOrOrigin != cOrigin)
				continue;

			if (! pSelectedRgn->IsWTR () && ! LevelIsVisible (origin.cLevel, pSelectedRgn->uchEndLevel))
				continue;

			if (! _rectVisible.Contains (origin.point))
				continue;

			// Calculate screen coordinates for POI and label.
			const int orig_x = XToScreen (origin.point.x) + iXOffset;
			const int orig_y = YToScreen (origin.point.y) + iYOffset;

			::SelectObject (_hDC, g_hpenBlackThin);
			::SelectObject (_hDC, g_hbrNull);
			::SetBkMode (_hDC, OPAQUE);
			::SetBkColor (_hDC, c_colWhite);
			
			::Ellipse (_hDC, orig_x - 8, orig_y - 8, orig_x + 9, orig_y + 9);

			::SetPixel (_hDC, orig_x, orig_y, c_colBlack);
		}
	}
}

void DrawSelection (HDC _hDC) {
	if (g_Selection.empty ())
		return;

	rect_t rectVisible;
	GetVisibleRect (rectVisible);

	for (size_t cSelItem = 0; cSelItem < g_Selection.size (); ++ cSelItem)
		_DrawSelection (_hDC, rectVisible, g_Selection [cSelItem]);
}

void SetSelectionOffset (int _iXOffset, int _iYOffset) {
	g_iXOffset = _iXOffset;
	g_iYOffset = _iYOffset;
}
